﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: Md5ConcurrencyManager.cs
//
//  Description: Implements a check sum concurrency mechanism.
// 
//--------------------------------------------------------------------------

using System;
using Chili.Opf3.Query;

namespace Chili.Opf3.Concurrency.Md5
{
	/// <summary>
	/// Implements the MD5 concurrency manager. This class uses a Md5 check
	/// to discover if somebody else has changed the object in the meantime.
	/// </summary>
	/// <remarks>
	/// This class implements a MD5 check that is used to validate an object against the storage.
	/// It checks if somebody else changed the object in the meantime (while it has been in memory)
	/// and informs the <see cref="Opf3.ObjectContext">ObjectContext</see> whether a problem has
	/// been encountered.
	/// </remarks>
	/// <example>
	/// The following example shows the usage of the class in your application.
	/// <code lang="CS">
	/// // ... Other code
	/// 
	/// // Creates a new ObjectContext that uses an MsSql Server as storage.
	/// ObjectContext context = new ObjectContext(new MsSqlStorage("sa", "", "localhost", "application"));
	/// // Set the MD5 Concurrency Manager. The ObjectContext uses it from 
	/// // now on to check the concurrency.
	/// context.ConcurrencyManager = new Md5ConcurrencyManager();
	/// 
	/// // ... Other code
	/// </code>
	/// </example>
	public sealed class Md5ConcurrencyManager : IConcurrencyManager
	{
		#region IConcurrencyManager Members

		/// <summary>
		/// Invoked before the update operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		/// <exception cref="Opf3.Concurrency.ConcurrencyException">A concurrency problem has been encountered.</exception>
		public void OnBeforeUpdate<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Reload the object from the storage.
			using (ObjectReader<T> reader = e.Context.GetObjectReader<T>(ObjectExpression.CreateLoadExpression(obj)))
			{
				// Somebody else deleted the object. Throw a concurrency exception.
				if (!reader.Read())
					throw new ConcurrencyException("A concurrency exception happened: Persistent not found in storage.", obj, null);

				// Get the checksum of the reloaded item.
				byte[] reloadedChecksum = e.Context.GetObjectInfo(reader.Current).Checksum;
				if (ChecksumBuilder.EqualsChecksum(reloadedChecksum, e.ObjectInfo.Checksum))
					return;

				// Throw a concurrency exception here.
				throw new ConcurrencyException("A concurrency exception happened: Checksums different.", obj, reader.Current);
			}
		}

		/// <summary>
		/// Invoked after the update operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		public void OnAfterUpdate<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Here is nothing to do.
		}

		/// <summary>
		/// Invoked before the insert operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		public void OnBeforeInsert<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Here is nothing to do.
		}

		/// <summary>
		/// Invoked after the insert operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		public void OnAfterInsert<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Here is nothing to do.
		}

		/// <summary>
		/// Invoked before the delete operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		public void OnBeforeDelete<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Here is nothing to do.
		}

		/// <summary>
		/// Invoked after the load operation of the object.
		/// </summary>
		/// <param name="obj">Persistent object that is validated.</param>
		/// <param name="e">EventArgs of the current routine.</param>
		public void OnAfterLoad<T>(T obj, ConcurrencyManagerEventArgs e)
		{
			// Here is nothing to do.
		}

		#endregion
	}
}
